# Caching AI planning & locate

Midscene supports caching Plan steps and matched DOM element information to reduce AI model calls and greatly improve execution efficiency. Please note that DOM element cache is only supported for web automation tasks.

**Effect**

With caching hit, time cost is significantly reduced. For example, in the following case, execution time was reduced from 51 seconds to 28 seconds.

* **before**

![](/cache/no-cache-time.png)

* **after**

![](/cache/use-cache-time.png)

## Cache files and storage

Midscene's caching mechanism is based on input stability and output reusability. When the same task instructions are repeatedly executed in similar page environments, Midscene will prioritize using cached results to avoid repeated AI model calls, significantly improving execution efficiency.

The core caching mechanisms include:
- **Task instruction caching**: For planning operations (such as `ai`, `aiAction`), Midscene uses the prompt instruction as the cache key to store the execution plan returned by AI
- **Element location caching**: For location operations (such as `aiLocate`, `aiTap`), the system uses the location prompt as the cache key to store element XPath information, and verifies whether the XPath is still valid on the next execution
- **Invalidation mechanism**: When cache becomes invalid, the system automatically falls back to AI model for re-analysis
- **Never cache query results**: The query results like `aiBoolean`, `aiQuery`, `aiAssert` will never be cached.

Cache contents will be saved in the `./midscene_run/cache` directory with the `.cache.yaml` as the extension name.

## Cache strategies

By configuring the `cache` option, you can enable caching for your agent.

### Disable cache

Configuration: `cache: false` or not configuring the `cache` option

Completely disable cache functionality, always call AI model for every operation. Suitable when you need real-time results or for debugging. By default, if you don't configure the `cache` option, caching is disabled.

```javascript
// Direct Agent creation
const agent = new PuppeteerAgent(page, {
  cache: false,
});
```

```yaml
# YAML configuration
agent:
  cache: false
```

### Read-write mode

Configuration: `cache: { id: "my-cache-id" }` or `cache: { strategy: "read-write", id: "my-cache-id" }`

Automatically read existing cache and update cache files during execution. The default value of `strategy` is `read-write`.

```javascript
// Direct Agent creation - explicit cache ID
const agent = new PuppeteerAgent(page, {
  cache: { id: "my-cache-id" },
});

// Explicitly specify strategy
const agent = new PuppeteerAgent(page, {
  cache: { strategy: "read-write", id: "my-cache-id" },
});
```

```yaml
# YAML configuration - explicit cache ID
agent:
  cache:
    id: "my-cache-test"

# Explicitly specify strategy
agent:
  cache:
    id: "my-cache-test"
    strategy: "read-write"
```

YAML mode also supports `cache: true` to automatically use the file name as the cache ID.

### Read-only, manual write

Configuration: `cache: { strategy: "read-only", id: "my-cache-id" }`

Only read cache, no automatic writing to cache files. Requires manual `agent.flushCache()` call to write cache files. Suitable for production environments to ensure cache consistency.

```javascript
// Direct Agent creation
const agent = new PuppeteerAgent(page, {
  cache: { strategy: "read-only", id: "my-cache-id" },
});

// Manual cache write required
await agent.flushCache();
```

```yaml
# YAML configuration
agent:
  cache:
    id: "my-cache-test"
    strategy: "read-only"
```

### Write-only mode

Configuration: `cache: { strategy: "write-only", id: "my-cache-id" }`

Only write to cache, do not read existing cache contents. Always call AI model for each execution and write results to cache file. Suitable for initially building cache or updating cache.

```javascript
// Direct Agent creation
const agent = new PuppeteerAgent(page, {
  cache: { strategy: "write-only", id: "my-cache-id" },
});
```

```yaml
# YAML configuration
agent:
  cache:
    id: "my-cache-test"
    strategy: "write-only"
```

### Compatibility mode (not recommended)

Configuration via `MIDSCENE_CACHE=1` environment variable with cacheId, equivalent to read-write mode.

```javascript
// Old way, requires MIDSCENE_CACHE=1 environment variable and cacheId
const agent = new PuppeteerAgent(originPage, {
  cacheId: 'puppeteer-swag-sab'
});
```

```bash
MIDSCENE_CACHE=1 tsx demo.ts
```

## Using Playwright AI Fixture from `@midscene/web/playwright`

When using `PlaywrightAiFixture` from `@midscene/web/playwright`, pass the same `cache` options to control caching behaviour.

### Disable cache

```typescript
// fixture.ts in sample code
export const test = base.extend<PlayWrightAiFixtureType>(
  PlaywrightAiFixture({
    cache: false,
  }),
);
```

### Read-write mode

```typescript
// fixture.ts in sample code
// Auto-generate cache ID from test metadata
export const test = base.extend<PlayWrightAiFixtureType>(
  PlaywrightAiFixture({
    cache: true,
  }),
);

// fixture.ts in sample code
// Explicitly provide cache ID
export const test = base.extend<PlayWrightAiFixtureType>(
  PlaywrightAiFixture({
    cache: { id: "my-fixture-cache" },
  }),
);
```

### Read-only, manual write

```typescript
// fixture.ts in sample code
export const test = base.extend<PlayWrightAiFixtureType>(
  PlaywrightAiFixture({
    cache: { strategy: "read-only", id: "readonly-cache" },
  }),
);
```

When you run the fixture in read-only mode you need to manually persist the cache after your test steps. Use the `agentForPage` helper provided by the fixture to fetch the underlying agent, then call `agent.flushCache()` at the point where you want to write the cache file:

```typescript
test.afterEach(async ({ page, agentForPage }, testInfo) => {
  // Only flush cache if the test passed
  if (testInfo.status === 'passed') {
    console.log('Test passed, flushing Midscene cache...');
    const agent = await agentForPage(page);
    await agent.flushCache();
  } else {
    console.log(`Test ${testInfo.status}, skipping Midscene cache flush.`);
  }
});

test('manual cache flush', async ({ agentForPage, page, aiTap, aiWaitFor }) => {
  const agent = await agentForPage(page);

  await aiTap('first highlighted link in the hero section');
  await aiWaitFor('the detail page loads completely');

  await agent.flushCache();
});
```

### Write-only mode

```typescript
// fixture.ts in sample code
export const test = base.extend<PlayWrightAiFixtureType>(
  PlaywrightAiFixture({
    cache: { strategy: "write-only", id: "write-only-cache" },
  }),
);
```

In write-only mode, each test will call the AI model and automatically write results to cache file without reading existing cache.

## Cache Cleaning

Midscene supports cleaning unused cache records when flushing cache to file, ensuring cache files stay lean and maintainable. This feature is **completely manual** and requires explicit call to `agent.flushCache({ cleanUnused: true })`.

### Manual Cleaning Mechanism

When calling `agent.flushCache({ cleanUnused: true })`, the system will:

1. **Keep used caches**: Cache records that were matched and used in the current run will be retained
2. **Keep new caches**: Cache records newly generated in the current run will be retained
3. **Remove unused caches**: Old cache records that were not accessed will be automatically deleted
4. **Write to file**: The cleaned cache will be written to file

### Usage

**Call in test afterEach:**

```javascript
describe('test suite', () => {
  let resetFn: () => Promise<void>;
  let agent: PuppeteerAgent;

  afterEach(async () => {
    // Clean cache and write to file
    if (agent) {
      await agent.flushCache({ cleanUnused: true });
    }

    // Then close the page
    if (resetFn) {
      await resetFn();
    }
  });

  it('test case', async () => {
    const { originPage, reset } = await launchPage('https://example.com/');
    resetFn = reset;
    agent = new PuppeteerAgent(originPage, {
      cache: { id: 'my-cache-id' },
    });

    // ... test logic
  });
});
```

**For Playwright AI Fixture users:**

```typescript
test.afterEach(async ({ page, agentForPage }) => {
  const agent = await agentForPage(page);
  await agent.flushCache({ cleanUnused: true });
});
```

### Cleanup Behavior by Mode

- **read-write mode**: Calling `flushCache({ cleanUnused: true })` will clean and write to file
- **read-only mode**: Calling `flushCache({ cleanUnused: true })` will also clean and write to file (manual flush overrides read-only restriction)
- **write-only mode**: No cleanup (does not read cache)

**Note**: If you don't pass `cleanUnused: true` parameter, `flushCache()` will only write to file without cleaning unused caches.

## FAQ

### No cache file is generated

Please ensure you have correctly configured caching:

1. **Direct Agent creation**: Set `cache: { id: "your-cache-id" }` in the constructor
2. **Playwright AI Fixture mode**: Set `cache: true` or `cache: { id: "your-cache-id" }` in fixture configuration
3. **YAML script mode**: Set `agent.cache.id` in the YAML file
4. **Read-only mode**: Ensure you called the `agent.flushCache()` method
5. **Legacy approach**: Set `cacheId` and enable `MIDSCENE_CACHE=1` environment variable

### How to check if the cache is hit?

You can view the report file. If the cache is hit, you will see the `cache` tip and the time cost is obviously reduced.

### Why the cache is missed on CI?

You need to commit the cache files to the repository in CI and recheck the cache hit conditions.

### Does it mean that AI services are no longer needed after using cache?

No. Caching is the way to accelerate the execution, but it's not a tool for ensuring long-term script stability. We have noticed many scenarios where the cache may miss when the DOM structure changes. AI services are still needed to reevaluate the task when the cache miss occurs.

### How to manually remove the cache?

You can remove the cache file in the `./midscene_run/cache` directory, or edit the contents in the cache file.

### How to disable the cache for a single API?

You can use the `cacheable` option to disable the cache for a single API.

Please refer to the documentation of the corresponding [API](./api.mdx) for details.

### Limitations of XPath in caching element location

Midscene uses [XPath](https://developer.mozilla.org/en-US/docs/Web/XML/XPath) to cache the element location. ⁠We are using a relatively strict strategy to prevent false matches. In these situations, the cache will not be accessed.

1. The text content of the new element at the same XPath is different from the cached element.
2. The DOM structure of the page is changed from the cached one.

When the cache is not hit, the process will fall back to continue using AI services to find the element.

### Get more debug logs for caching

You can set the `DEBUG=midscene:cache:*` environment variable to get more debug logs for caching.
